using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.IO;


namespace MindFactorial.LaunchPoint.Common
{
    
    /// <summary>
    /// Represents a serializable version of xml as either a string, document or node
    /// also provides facilities for implicit conversion between the various aforementioned
    /// types.  The xml is maintained internally as a lightwieght string until a call to the
    /// appropriate property does an in place conversion so it is safe to use as a container for
    /// LaunchPointsaging.
    /// <remarks>
    /// Note that an Xml object can be implicitly converted to and from XmlDocument, XmlNode, and string
    /// and that strings that are not well formed will be wrapped in a well formed envelope with the element name
    /// xml and placed in a cdata section.  For instance, if I run the code 
    /// <code>
    /// Xml x = "hello world";
    /// Console.WriteLine(x);
    /// </code>
    /// I will get the result: <xml><![CDATA[hello world]]></xml>
    /// </remarks>
    /// </summary>
    [Serializable]
    public class Xml : LaunchPointObject
    {
        string _xml = null;
        bool _isvalid = false;

        internal Xml()
        {

        }


        /// <summary>
        /// Gets the internal string representation of the xml object
        /// </summary>
        public string Value
        {
            get { return _xml; }
        }

        /// <summary>
        /// Gets a value indicating whether the internal xml is valid after a callin gLoad on it.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return _isvalid;
            }
            
        }

        /// <summary>
        /// Converts string to XmlTextReader  
        /// </summary>
        public XmlReader Reader
        {
            get
            {
                try
                {
                    StringReader sr = new StringReader(this.Value);
                    XmlTextReader xtr = new XmlTextReader(sr);
                    return xtr;
                }
                catch (Exception)
                {                    
                    return null;
                }
            }
        }

       

        /// <summary>
        /// Gets the XmlNode associated with this object
        /// </summary>
        public XmlNode Node
        {
            get
            {
                try
                {
                    XmlDocument xdoc = new XmlDocument();
                    xdoc.LoadXml(this.Value);                    
                    return xdoc.DocumentElement;
                }
                catch (Exception)
                {                   
                    return null;
                }
            }
        }

        /// <summary>
        /// Gets the xml document associated with this object
        /// </summary>
        public XmlDocument Document
        {
            get
            {
                try
                {
                    XmlDocument xdoc = new XmlDocument();
                    xdoc.LoadXml(this.Value);
                    return xdoc;
                }
                catch (Exception)
                {                    
                    return null;
                }
            }
        }

        /// <summary>
        /// returns the string representation of a string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this._xml;
        }

        /// <summary>
        /// Converts an xml object into an xml node
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        public static implicit operator System.Xml.XmlNode(Xml xml)
        {
           return xml.Node; 
        }

        /// <summary>
        /// Converts an xml document into the  XmlObject representation of a document
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        public static implicit operator System.Xml.XmlDocument(Xml xml)
        {
           return xml.Document; 
        }

        /// <summary>
        /// Converts an XmlNode into an Xml object
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public static implicit operator Xml(System.Xml.XmlNode node)
        {
            Xml xml = new Xml();
            xml._xml = node.OuterXml;
            return xml;
        }

        /// <summary>
        /// Converts an XmlNode into an Xml Document
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static implicit operator Xml(System.Xml.XmlDocument document)
        {
            Xml xml = new Xml();
            xml._xml = document.DocumentElement.OuterXml;
            return xml;
        }



        /// <summary>
        /// Implicitly converts a string to an Xml object. Any string not passed 
        /// in xml format will be automatically placed
        /// </summary>
        /// <param name="xmlstring"></param>
        /// <returns></returns>
        public static implicit operator Xml(string xmlstring)
        {
            try
            {
                XmlDocument xdoc = new XmlDocument();
                xdoc.LoadXml(xmlstring);
                Xml xml = new Xml();
                xml._xml = xdoc.DocumentElement.OuterXml;
                xml._isvalid = true;
                return xml;
            }
            catch (Exception)
            {
                Xml xml = new Xml();
                xml._xml = xmlstring;
                xml._isvalid = false;
                return xml;               
            }
        }

        /// <summary>
        /// explicitly converts an exception into an xml object of a specific format. 
        /// 
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static explicit operator Xml(Exception ex)
        {
            return Xml.Format("<Exception><Message><![CDATA[{0}]]></Message><StackTrace><![CDATA[{1}]]></StackTrace></Exception>", ex.Message, ex.StackTrace);
        }

        /// <summary>
        /// Does all runtime related validation of schemas associated with applications
        /// </summary>
        /// <param name="path"></param>
        /// <param name="schemapath"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public static XmlReader GetValidatingReader(string path, XmlReader xsd_reader, ValidationHandler handler)
        {
            try
            {       
                
                //XmlReader.Create(
                XmlReaderSettings xrs = new XmlReaderSettings();
                xrs.ValidationType = ValidationType.Schema;
                xrs.Schemas.Add(null, xsd_reader);
                xrs.ValidationEventHandler += delegate(object sender, System.Xml.Schema.ValidationEventArgs e)
                {
                    handler(e);
                };
                XmlReader xreader = XmlReader.Create(path, xrs);
                return xreader;
            }
            catch (Exception ex)
            {
                throw new Exception("{55C89C1D-4D68-4480-A767-8FA53F49BF93}: file " + path +" could not be validated against " + xsd_reader.BaseURI  + " because " + ex.Message);
            }
        }

        /// <summary>
        /// Does all runtime related validation of schemas associated with applications
        /// </summary>
        /// <param name="path"></param>
        /// <param name="schemapath"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public static XmlReader GetValidatingReader(Stream input, XmlReader xsd_reader, ValidationHandler handler)
        {
            try
            {
                XmlReaderSettings xrs = new XmlReaderSettings();
                xrs.ValidationType = ValidationType.Schema;
                xrs.Schemas.Add(null, xsd_reader);
                xrs.ValidationEventHandler += delegate(object sender, System.Xml.Schema.ValidationEventArgs e)
                {
                    handler(e);
                };
                XmlReader xreader = XmlReader.Create(input, xrs);
                return xreader;
            }
            catch (Exception ex)
            {
                throw new Exception("3F964274-4DC9-422c-B205-60B23ADA802B: stream could not be validated against " + xsd_reader.BaseURI + " because " + ex.Message);
            }
        }

        /// <summary>
        /// Loads a filepath into the Xml object
        /// </summary>
        /// <param name="filepath"></param>
        /// <param name="schemapath"></param>
        /// <returns></returns>
        public static Xml Load(Stream stream, XmlReader xsd_reader)
        {
            bool isvalid = true;
            XmlReader xreader = Xml.GetValidatingReader(stream, xsd_reader, delegate(ValidationEventArgs e)
            {
                isvalid = false;
            });


            Xml xml = null;
            //attempt to read file
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(xreader);
            xreader.Close();

            if (isvalid) //if valid pass into xml document and load the document
            {
                xml = xdoc;
            }
            else        //else load it as string into Xml object and allow Xml object to create a document
            {
                StreamReader reader = new StreamReader(stream);
                string filedata = reader.ReadToEnd();
                reader.Close();
                xml = Xml.Format(filedata);
            }

            //set validation level and return
            xml._isvalid = isvalid;
            return xml;
        }

        /// <summary>
        /// Loads a filepath into the Xml object
        /// </summary>
        /// <param name="filepath"></param>
        /// <param name="schemapath"></param>
        /// <returns></returns>
        public static Xml Load(string filepath, XmlReader xsd_reader)
        {
            bool isvalid = true;
            XmlReader xreader = Xml.GetValidatingReader(filepath, xsd_reader, delegate(ValidationEventArgs e)
                            {
                                isvalid = false;                                
                            });
            
            
            Xml xml = null;
            //attempt to read file
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(xreader);
            xreader.Close();

            if (isvalid) //if valid pass into xml document and load the document
            {               
                xml = xdoc;                
            }
            else        //else load it as string into Xml object and allow Xml object to create a document
            {
                string filedata = File.ReadAllText(filepath);
                xml = Xml.Format(filedata);               
            }            
            
            //set validation level and return
            xml._isvalid = isvalid;
            return xml;
        }

        /// <summary>
        ///  Load the Xml object from a format string
        /// <example>
        /// <code>
        ///     Xml xml = Xml.Format("<data>{0}</data>",5);
        /// </code>
        /// </example>
        /// </summary>
        /// <param name="format">specified String</param>
        /// <param name="args">arguments</param>
        /// <returns></returns>
        public static Xml Format(string format, params string[] args)
        {
            string xml = string.Format(format, args);
            return (Xml)xml;
        }

        /// <summary>
        /// Formats any given type in xml form
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="arg"></param>
        /// <returns></returns>
        public static Xml Format<T>(T arg)
        {
            return string.Format("<xml><![CDATA[{0}]]></xml>", arg);
        }
    }
}
